summaryrefslogtreecommitdiff
path: root/app/[lng]/partners/(partners)/_dolce-upload/dolce-upload-page.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-27 09:44:05 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-27 09:44:05 +0000
commit24c19f0193cab4df20510e2764c0d0fe158e3842 (patch)
tree168ff80cf3c9bec667bd0056d3f50722582c151a /app/[lng]/partners/(partners)/_dolce-upload/dolce-upload-page.tsx
parentdaabc02e9ae54f216ada77aa826b349f37c3281a (diff)
parent5ca88c4869be338f4b0e506a679e4dc4e029d7aa (diff)
Merge branch 'dujinkim' of https://github.com/DTS-Development/SHI_EVCP into dujinkim
Diffstat (limited to 'app/[lng]/partners/(partners)/_dolce-upload/dolce-upload-page.tsx')
-rw-r--r--app/[lng]/partners/(partners)/_dolce-upload/dolce-upload-page.tsx420
1 files changed, 420 insertions, 0 deletions
diff --git a/app/[lng]/partners/(partners)/_dolce-upload/dolce-upload-page.tsx b/app/[lng]/partners/(partners)/_dolce-upload/dolce-upload-page.tsx
new file mode 100644
index 00000000..1bb876fb
--- /dev/null
+++ b/app/[lng]/partners/(partners)/_dolce-upload/dolce-upload-page.tsx
@@ -0,0 +1,420 @@
+"use client";
+
+import { useState, useEffect, useCallback, useMemo } from "react";
+import { useParams } from "next/navigation";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { InfoIcon, RefreshCw, Search, Upload } from "lucide-react";
+import { toast } from "sonner";
+import { useTranslation } from "@/i18n/client";
+import {
+ UnifiedDwgReceiptItem,
+ fetchDwgReceiptList,
+ getVendorSessionInfo,
+ fetchVendorProjects,
+} from "@/lib/dolce/actions";
+import { DrawingListTable } from "@/lib/dolce/table/drawing-list-table";
+import { drawingListColumns } from "@/lib/dolce/table/drawing-list-columns";
+import { createGttDrawingListColumns, DocumentType } from "@/lib/dolce/table/gtt-drawing-list-columns";
+import { DetailDrawingDialog } from "@/lib/dolce/dialogs/detail-drawing-dialog";
+// V2: MatchBatchFileDwg+MatchBatchFileDwgEdit API 사용, 별도의 RegisterKind 선택 없이 결과값 기준으로 업로드
+import { B4BulkUploadDialogV3 } from "@/lib/dolce/dialogs/b4-bulk-upload-dialog-v3";
+// V1로 되돌리려면: 위 줄을 주석 처리하고 아래 줄의 주석을 해제하세요
+// import { B4BulkUploadDialog } from "@/lib/dolce/dialogs/b4-bulk-upload-dialog";
+
+interface DolceUploadPageProps {
+ searchParams: { [key: string]: string | string[] | undefined };
+}
+
+export default function DolceUploadPage({ searchParams }: DolceUploadPageProps) {
+ const params = useParams();
+ const lng = params?.lng as string;
+ const { t } = useTranslation(lng, "dolce");
+
+ // URL에서 초기 프로젝트 코드
+ const initialProjNo = (searchParams.projNo as string) || "";
+
+ // 상태 관리
+ const [drawings, setDrawings] = useState<UnifiedDwgReceiptItem[]>([]);
+ const [projects, setProjects] = useState<Array<{ code: string; name: string }>>([]);
+ const [vendorInfo, setVendorInfo] = useState<{
+ userId: string;
+ userName: string;
+ email: string;
+ vendorCode: string;
+ vendorName: string;
+ drawingKind: "B3" | "B4";
+ } | null>(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isRefreshing, setIsRefreshing] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+ // 필터 상태
+ const [projNo, setProjNo] = useState(initialProjNo);
+ const [drawingNo, setDrawingNo] = useState("");
+ const [drawingName, setDrawingName] = useState("");
+ const [discipline, setDiscipline] = useState("");
+ const [manager, setManager] = useState("");
+ const [documentType, setDocumentType] = useState<DocumentType>("ALL"); // B4 전용
+
+ // 선택된 도면 (다이얼로그용)
+ const [selectedDrawing, setSelectedDrawing] = useState<UnifiedDwgReceiptItem | null>(null);
+ const [dialogOpen, setDialogOpen] = useState(false);
+
+ // 일괄 업로드 다이얼로그
+ const [bulkUploadDialogOpen, setBulkUploadDialogOpen] = useState(false);
+
+ // 초기 데이터 로드
+ const loadInitialData = useCallback(async () => {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ // 병렬로 데이터 로드
+ const [vendorInfoData, projectsData] = await Promise.all([
+ getVendorSessionInfo(),
+ fetchVendorProjects(),
+ ]);
+
+ setVendorInfo(vendorInfoData as typeof vendorInfo);
+ setProjects(projectsData);
+
+ // 초기 프로젝트가 있으면 도면 로드
+ if (initialProjNo) {
+ const drawingsData = await fetchDwgReceiptList({
+ project: initialProjNo,
+ drawingKind: vendorInfoData.drawingKind,
+ drawingVendor: vendorInfoData.drawingKind === "B3" ? vendorInfoData.vendorCode : "",
+ });
+ setDrawings(drawingsData);
+ }
+ } catch (err) {
+ console.error("초기 데이터 로드 실패:", err);
+ setError(err instanceof Error ? err.message : t("page.initialLoadError"));
+ toast.error(t("page.initialLoadError"));
+ } finally {
+ setIsLoading(false);
+ }
+ }, [initialProjNo, t]);
+
+ // 도면 목록 조회
+ const loadDrawings = useCallback(async () => {
+ if (!projNo || !vendorInfo) return;
+
+ try {
+ setIsRefreshing(true);
+ setError(null);
+
+ const drawingsData = await fetchDwgReceiptList({
+ project: projNo,
+ drawingKind: vendorInfo.drawingKind,
+ drawingVendor: vendorInfo.drawingKind === "B3" ? vendorInfo.vendorCode : "",
+ });
+
+ setDrawings(drawingsData);
+ toast.success(t("page.drawingLoadSuccess"));
+ } catch (err) {
+ console.error("도면 로드 실패:", err);
+ setError(err instanceof Error ? err.message : t("page.drawingLoadError"));
+ toast.error(t("page.drawingLoadError"));
+ } finally {
+ setIsRefreshing(false);
+ }
+ }, [projNo, vendorInfo, t]);
+
+ // 초기 데이터 로드
+ useEffect(() => {
+ loadInitialData();
+ }, [loadInitialData]);
+
+ // 프로젝트 변경 시 자동 검색
+ useEffect(() => {
+ if (projNo && vendorInfo) {
+ loadDrawings();
+ }
+ }, [projNo, vendorInfo, loadDrawings]);
+
+ // 도면 클릭 핸들러
+ const handleDrawingClick = (drawing: UnifiedDwgReceiptItem) => {
+ setSelectedDrawing(drawing);
+ setDialogOpen(true);
+ };
+
+ // 검색 핸들러
+ const handleSearch = () => {
+ loadDrawings();
+ };
+
+ // 새로고침 핸들러
+ const handleRefresh = () => {
+ loadDrawings();
+ };
+
+ // 일괄 업로드 완료 핸들러
+ const handleBulkUploadComplete = () => {
+ loadDrawings();
+ };
+
+ // 필터된 도면 목록 (클라이언트 사이드 필터링)
+ const filteredDrawings = useMemo(() => {
+ let result = drawings.filter((drawing) => {
+ // 도면번호 필터 (공백 포함)
+ if (drawingNo && !drawing.DrawingNo.toLowerCase().includes(drawingNo.toLowerCase())) {
+ return false;
+ }
+
+ // 도면명 필터 (공백 포함)
+ if (drawingName && !drawing.DrawingName.toLowerCase().includes(drawingName.toLowerCase())) {
+ return false;
+ }
+
+ // 설계공종 필터 (공백 포함)
+ if (discipline && !drawing.Discipline?.toLowerCase().includes(discipline.toLowerCase())) {
+ return false;
+ }
+
+ // 담당자명 필터 (공백 포함)
+ if (manager && !drawing.Manager.toLowerCase().includes(manager.toLowerCase()) &&
+ !drawing.ManagerENM?.toLowerCase().includes(manager.toLowerCase())) {
+ return false;
+ }
+
+ return true;
+ });
+
+ // B4인 경우 Document Type 필터 적용
+ if (vendorInfo?.drawingKind === "B4" && documentType !== "ALL") {
+ result = result.filter((drawing) => {
+ // B4 타입 체크
+ if (drawing.DrawingKind !== "B4") return false;
+
+ // B4 도면의 DrawingMoveGbn 체크
+ const gttDrawing = drawing as { DrawingMoveGbn?: string };
+
+ if (documentType === "SHI_INPUT") {
+ return gttDrawing.DrawingMoveGbn === "도면제출";
+ } else if (documentType === "GTT_DELIVERABLES") {
+ return gttDrawing.DrawingMoveGbn === "도면입수";
+ }
+ return true;
+ });
+ }
+
+ return result;
+ }, [drawings, drawingNo, drawingName, discipline, manager, vendorInfo?.drawingKind, documentType]);
+
+ if (isLoading) {
+ return (
+ <Card>
+ <CardHeader>
+ <Skeleton className="h-8 w-48" />
+ <Skeleton className="h-4 w-96" />
+ </CardHeader>
+ <CardContent className="space-y-4">
+ <Skeleton className="h-32 w-full" />
+ <Skeleton className="h-96 w-full" />
+ </CardContent>
+ </Card>
+ );
+ }
+
+ return (
+ <div className="space-y-6 max-w-full overflow-x-hidden">
+ {/* 에러 메시지 */}
+ {error && (
+ <Alert variant="destructive">
+ <AlertDescription>{error}</AlertDescription>
+ </Alert>
+ )}
+
+ {/* 안내 메시지 */}
+ {!projNo && (
+ <Alert>
+ <InfoIcon className="h-4 w-4" />
+ <AlertDescription>
+ {t("page.selectProject")}
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {/* 필터 카드 */}
+ <Card>
+ <CardHeader>
+ <CardTitle>{t("filter.title")}</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
+ {/* 프로젝트 선택 */}
+ <div className="space-y-2">
+ <Label>{t("filter.project")}</Label>
+ <Select value={projNo} onValueChange={setProjNo}>
+ <SelectTrigger>
+ <SelectValue placeholder={t("filter.projectPlaceholder")} />
+ </SelectTrigger>
+ <SelectContent>
+ {projects.map((project) => (
+ <SelectItem key={project.code} value={project.code}>
+ {project.code} - {project.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+
+ {/* 도면번호 검색 */}
+ <div className="space-y-2">
+ <Label>{t("filter.drawingNo")}</Label>
+ <Input
+ value={drawingNo}
+ onChange={(e) => setDrawingNo(e.target.value)}
+ placeholder={t("filter.drawingNoPlaceholder")}
+ />
+ </div>
+
+ {/* 도면명 검색 */}
+ <div className="space-y-2">
+ <Label>{t("filter.drawingName")}</Label>
+ <Input
+ value={drawingName}
+ onChange={(e) => setDrawingName(e.target.value)}
+ placeholder={t("filter.drawingNamePlaceholder")}
+ />
+ </div>
+
+ {/* 설계공종 검색 */}
+ <div className="space-y-2">
+ <Label>{t("filter.discipline")}</Label>
+ <Input
+ value={discipline}
+ onChange={(e) => setDiscipline(e.target.value)}
+ placeholder={t("filter.disciplinePlaceholder")}
+ />
+ </div>
+
+ {/* 담당자명 검색 (클라이언트 필터) */}
+ <div className="space-y-2">
+ <Label>{t("filter.manager")}</Label>
+ <Input
+ value={manager}
+ onChange={(e) => setManager(e.target.value)}
+ placeholder={t("filter.managerPlaceholder")}
+ />
+ </div>
+
+ {/* B4(GTT) 전용: Document Type 필터 */}
+ {vendorInfo?.drawingKind === "B4" && (
+ <div className="space-y-2">
+ <Label>{t("filter.documentType")}</Label>
+ <Select value={documentType} onValueChange={(value) => setDocumentType(value as DocumentType)}>
+ <SelectTrigger>
+ <SelectValue />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectItem value="ALL">{t("filter.documentTypeAll")}</SelectItem>
+ <SelectItem value="GTT_DELIVERABLES">{t("filter.documentTypeGttDeliverables")}</SelectItem>
+ <SelectItem value="SHI_INPUT">{t("filter.documentTypeSHIInput")}</SelectItem>
+ </SelectContent>
+ </Select>
+ </div>
+ )}
+ </div>
+
+ <div className="flex gap-2 mt-4 justify-end">
+ <Button
+ onClick={handleSearch}
+ disabled={!projNo || isRefreshing}
+ >
+ <Search className="h-4 w-4 mr-2" />
+ {t("filter.searchButton")}
+ </Button>
+ {/* B4 벤더인 경우에만 일괄 업로드 버튼 표시 */}
+ {vendorInfo?.drawingKind === "B4" && (
+ <Button
+ variant="default"
+ onClick={() => setBulkUploadDialogOpen(true)}
+ disabled={!projNo || isRefreshing}
+ >
+ <Upload className="h-4 w-4 mr-2" />
+ {t("filter.bulkUploadButton")}
+ </Button>
+ )}
+ <Button
+ variant="outline"
+ onClick={handleRefresh}
+ disabled={!projNo || isRefreshing}
+ >
+ <RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
+ </Button>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 도면 리스트 테이블 */}
+ {projNo && vendorInfo && (
+ <Card>
+ <CardHeader>
+ <CardTitle>
+ {t("drawingList.title")}
+ {filteredDrawings.length > 0 && ` ${t("drawingList.count", { count: filteredDrawings.length })}`}
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="overflow-x-auto">
+ <DrawingListTable
+ columns={
+ vendorInfo.drawingKind === "B4"
+ ? (createGttDrawingListColumns({ documentType, lng, t }) as unknown as typeof drawingListColumns)
+ : (drawingListColumns(lng, t) as unknown as typeof drawingListColumns)
+ }
+ data={filteredDrawings}
+ onRowClick={handleDrawingClick}
+ />
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 상세도면 다이얼로그 */}
+ {vendorInfo && (
+ <DetailDrawingDialog
+ drawing={selectedDrawing}
+ open={dialogOpen}
+ onOpenChange={setDialogOpen}
+ vendorCode={vendorInfo.vendorCode}
+ userId={vendorInfo.userId}
+ userName={vendorInfo.userName}
+ userEmail={vendorInfo.email}
+ drawingKind={vendorInfo.drawingKind}
+ lng={lng}
+ />
+ )}
+
+ {/* B4 일괄 업로드 다이얼로그 (V2) */}
+ {/* V2: MatchBatchFileDwg+MatchBatchFileDwgEdit API 사용, 별도의 RegisterKind 선택 없이 결과값 기준으로 업로드 */}
+ {vendorInfo && vendorInfo.drawingKind === "B4" && projNo && (
+ <B4BulkUploadDialogV3
+ open={bulkUploadDialogOpen}
+ onOpenChange={setBulkUploadDialogOpen}
+ projectNo={projNo}
+ userId={vendorInfo.userId}
+ userName={vendorInfo.userName}
+ userEmail={vendorInfo.email}
+ vendorCode={vendorInfo.vendorCode}
+ onUploadComplete={handleBulkUploadComplete}
+ lng={lng}
+ />
+ )}
+ {/* V1로 되돌리려면: 위의 B4BulkUploadDialogV2를 B4BulkUploadDialog로 변경하세요 */}
+ </div>
+ );
+}
+